package org.finesys.common.des.uitls;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.util.Arrays;
import java.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;

/**
 * sm4加解密工具类
 */
public class SM4Util {

    public static String ALGORITHM = "SM4";
    public static String CHARSET_NAME = "UTF-8";
    // 加密模式
    public static String ALGORITHM_ECB_PADDING = "SM4/ECB/PKCS5Padding";
    public static String ALGORITHM_CBC_PADDING = "SM4/CBC/PKCS5Padding";

    // 填充模式
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 使用ECB模式加密字符串。
     *
     * @param key  密钥字符串。
     * @param data 待加密的字符串数据。
     * @return 加密后的16进制字符串。
     * @throws UnsupportedEncodingException 如果字符集不被支持。
     */
    public static String encryptEcb(String key, String data) throws Exception {
        //String--> byte[]
        byte[] keyBytes = Hex.decode(key);
        //String--> byte[]
        byte[] srcData = data.getBytes(CHARSET_NAME);
        //加密后数组
        byte[] chipperData = encryptEcbPadding(keyBytes, srcData);
        //加密后Base64编码
        return Base64.getEncoder().encodeToString(chipperData);
    }

    /**
     * 使用ECB模式和PKCS5Padding填充方式进行加密
     *
     * @param keyBytes 密钥字节数组
     * @param srcData  待加密的原始数据字节数组
     * @return 加密后的字节数组
     * @throws IllegalBlockSizeException 如果加密块大小非法，则抛出此异常
     * @throws BadPaddingException       如果填充不正确，则抛出此异常
     */
    public static byte[] encryptEcbPadding(byte[] keyBytes, byte[] srcData) throws Exception {
        Cipher cipher = generateEcbCipher(ALGORITHM_ECB_PADDING, Cipher.ENCRYPT_MODE, keyBytes);
        return cipher.doFinal(srcData);
    }

    /**
     * 生成ECB模式的Cipher对象
     *
     * @param algorithmEcbPadding 加密算法和填充方式，例如"AES/ECB/PKCS5Padding"
     * @param encryptMode         加密模式，例如Cipher.ENCRYPT_MODE（加密模式）或Cipher.DECRYPT_MODE（解密模式）
     * @param keyBytes            密钥字节数组
     * @return Cipher对象
     * @throws InvalidKeyException      如果密钥无效
     * @throws NoSuchPaddingException   如果请求的填充模式不可用
     * @throws NoSuchAlgorithmException 如果请求的加密算法不可用
     * @throws NoSuchProviderException  如果请求的提供者不可用
     */
    private static Cipher generateEcbCipher(String algorithmEcbPadding, int encryptMode, byte[] keyBytes) throws Exception {
        Cipher cipher = Cipher.getInstance(algorithmEcbPadding, BouncyCastleProvider.PROVIDER_NAME);
        cipher.init(encryptMode, new SecretKeySpec(keyBytes, ALGORITHM));
        return cipher;
    }

    /**
     * 使用ECB模式解密字符串数据
     *
     * @param key         用于解密的密钥字符串
     * @param chipperData 需要解密的十六进制加密数据字符串
     * @return 解密后的字符串
     * @throws Exception 解密过程中可能出现的异常
     */
    public static String decryptEcb(String key, String chipperData) throws Exception {
        byte[] keyBytes = Hex.decode(key);
        byte[] srcData = Base64.getDecoder().decode(chipperData);
        return new String(decryptEcbPadding(keyBytes, srcData), CHARSET_NAME);
    }

    /**
     * 使用ECB模式对给定的字节数组进行解密。
     *
     * @param keyBytes 解密密钥的字节数组。
     * @param srcData  待解密的字节数组。
     * @return 解密后的字节数组。
     * @throws Exception 如果解密过程中出现错误，则抛出异常。
     */
    public static byte[] decryptEcbPadding(byte[] keyBytes, byte[] srcData) throws Exception {
        Cipher cipher = generateEcbCipher(ALGORITHM_ECB_PADDING, Cipher.DECRYPT_MODE, keyBytes);
        return cipher.doFinal(srcData);
    }

    /**
     * 验证ECB加密的数据是否正确
     *
     * @param hexKey      加密使用的十六进制密钥
     * @param chipperText 加密后的密文
     * @param data        需要验证的原始数据
     * @return 如果验证通过返回true，否则返回false
     * @throws Exception 如果密钥或密文格式不正确，或者解密过程中出现错误，将抛出异常
     */
    public static boolean verifyEcb(String key, String chipperText, String data) throws Exception {
        byte[] srcData = data.getBytes(CHARSET_NAME);
        byte[] keyBytes = Hex.decode(key);
        byte[] chipperData = Base64.getDecoder().decode(chipperText);
        byte[] decryptData = decryptEcbPadding(keyBytes, chipperData);

        return Arrays.equals(srcData, decryptData);
    }


    /**
     * 生成ECB模式的Cipher对象
     *
     * @param algorithmEcbPadding 加密算法和填充方式，例如"AES/ECB/PKCS5Padding"
     * @param encryptMode         加密模式，例如Cipher.ENCRYPT_MODE（加密模式）或Cipher.DECRYPT_MODE（解密模式）
     * @param keyBytes            密钥字节数组
     * @return Cipher对象
     * @throws InvalidKeyException      如果密钥无效
     * @throws NoSuchPaddingException   如果请求的填充模式不可用
     * @throws NoSuchAlgorithmException 如果请求的加密算法不可用
     * @throws NoSuchProviderException  如果请求的提供者不可用
     */
    private static Cipher generateCbcCipher(String algorithmEcbPadding, int encryptMode, byte[] keyBytes, byte[] ivBytes) throws Exception {
        Cipher cipher = Cipher.getInstance(algorithmEcbPadding, BouncyCastleProvider.PROVIDER_NAME);
        cipher.init(encryptMode, new SecretKeySpec(keyBytes, ALGORITHM), new IvParameterSpec(ivBytes));
        return cipher;
    }

    /**
     * 使用CBC模式加密字符串。
     *
     * @param hexKey 加密密钥的16进制字符串表示
     * @param iv     初始化向量的16进制字符串表示
     * @param data   待加密的字符串
     * @return 加密后的16进制字符串
     * @throws Exception 如果加密过程中发生异常，则抛出该异常
     */
    public static String encryptCbc(String key, String iv, String data) throws Exception {
        //16进制
        byte[] keyBytes = Hex.decode(key);
        //Base64--> byte[]
        byte[] srcData = data.getBytes(CHARSET_NAME);
        //
        byte[] ivBytes = Hex.decode(iv);
        //加密后数组
        byte[] chipperData = encryptCbcPadding(keyBytes, ivBytes, srcData);
        //加密后16进制
        return Base64.getEncoder().encodeToString(chipperData);
    }

    /**
     * 使用CBC模式和PKCS5Padding填充方式对数据进行加密
     *
     * @param keyBytes 密钥字节数组
     * @param ivBytes  初始化向量字节数组
     * @param srcData  待加密的数据字节数组
     * @return 加密后的字节数组
     * @throws Exception 加密过程中可能出现的异常
     */
    public static byte[] encryptCbcPadding(byte[] keyBytes, byte[] ivBytes, byte[] srcData) throws Exception {
        Cipher cipher = generateCbcCipher(ALGORITHM_CBC_PADDING, Cipher.ENCRYPT_MODE, keyBytes, ivBytes);
        return cipher.doFinal(srcData);
    }

    /**
     * 使用CBC模式解密数据
     *
     * @param hexKey      用于解密的密钥字符串
     * @param iv          用于CBC模式的初始化向量（IV）
     * @param chipperData 待解密的密文数据，以十六进制字符串表示
     * @return 解密后的明文字符串
     * @throws Exception 如果解密过程中出现错误，抛出异常
     */
    public static String decryptCbc(String key, String iv, String chipperData) throws Exception {

        byte[] keyBytes = Hex.decode(key);
        //Base64--> byte[]
        byte[] srcData = Base64.getDecoder().decode(chipperData);
        //
        byte[] ivBytes = Hex.decode(iv);
//        hexStringToBytes(iv);
        return new String(decryptCbcPadding(keyBytes, ivBytes, srcData), CHARSET_NAME);
    }

    /**
     * 使用CBC模式和PKCS5Padding填充解密数据
     *
     * @param keyBytes 解密密钥字节数组
     * @param ivBytes  初始化向量字节数组
     * @param srcData  待解密的数据字节数组
     * @return 解密后的数据字节数组
     * @throws Exception 解密过程中出现的异常
     */
    private static byte[] decryptCbcPadding(byte[] keyBytes, byte[] ivBytes, byte[] srcData) throws Exception {
        Cipher cipher = generateCbcCipher(ALGORITHM_CBC_PADDING, Cipher.DECRYPT_MODE, keyBytes, ivBytes);
        return cipher.doFinal(srcData);
    }


    public static void main(String[] args) {
        String secretKey = "a81180a49b296c39ca1d5c7f15f7e56f";
        String iv = "0123456789abcdeffedcba9876543210";
        try {
            String planText = "1";
            String encryptData = encryptEcb(secretKey, planText);
            System.out.println("加密后：" + encryptData);
            String descryptData = decryptEcb(secretKey, encryptData);
            System.out.println("解密后：" + descryptData);


            String encryptCbcData = encryptCbc(secretKey, iv, planText);
            System.out.printf("CBC加密后：" + encryptCbcData);
            String decryptCbcData = decryptCbc(secretKey, iv, encryptCbcData);
            System.out.println("解密后：" + decryptCbcData);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
